﻿# -*- coding: utf-8 -*-
# Manifest.py

# Copyright 2012 Ryan Schultz

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

import os
import string

class Manifest:
	"""Handles the creation and reading of TaoSync manifest files."""
	def __init__(self, address = "noaddress", manifestaddress = "nomanifest", servername = "Default"):
		self.address = address
		self.servername = servername
		self.manifestaddress = manifestaddress
		self.file_list = []
		self.path = os.getcwd()
		self.manifest_dictionary = {}
	
	def make_file_list(self, path = os.getcwd(), verbose = False):
		"""
		Makes the file list for the manifest
		Keyword arguments:
		address -- HTTP address where the manifest will be stored (server path)
		path -- Path of the directory to hash and add to manifest (default current working directory)
		verbose -- Show extra info? (default False)
		"""
		
		self.path = path
		
		# Change to the path
		try:
			os.chdir(path)
		except:
			print "ERROR: Unable to change to directory %s" % path
			raise
			
			
		# Get a complete path so we don't have to deal with relative paths
		full_path = os.getcwd()
		self.path = full_path
		
		if verbose == True:
			print "INFO: Working location is", full_path
		
		# Only iterate over the first depth (the @addon folders, not their contents)
		for name in os.listdir(full_path):
			if os.path.isdir(os.path.join(full_path, name)): # If it's a directory, it's an addon folder so we want it	
				if verbose == True:
					print "INFO: Adding directory %s" % name
				
				self.file_list.append(name)
			else: # Wasn't a directory
				if verbose == True:
					print "INFO: Skipping file %s" % name
	
	def build_manifest(self, file_name):
		"""
		Builds and writes the manifest to an XML file, preparing files as it goes.
		
		Keyword arguments:
		file_name -- File to write to.
		"""
		
		import DirHasher
		import xml.etree.ElementTree as ET
		import shutil
		import hashlib
		
		dh = DirHasher.DirHasher()
		
		root = ET.Element('manifest')
		servername = ET.SubElement(root, 'servername')
		servername.text = self.servername
		address = ET.SubElement(root, 'address')
		address.text = self.address
		manifestaddress = ET.SubElement(root, 'manifestaddress')
		manifestaddress.text = self.manifestaddress
		addons = ET.SubElement(root, 'addons')
		
		for file in self.file_list:
			addon = ET.SubElement(addons, 'addon')
			dir = ET.SubElement(addon, 'dir')
			addonline = ET.SubElement(root, 'addonline')
			dir.text = file
			addonline.text = file + ";"
			dirhash = ET.SubElement(addon, 'dirhash')
			dirhash.text = dh.make_dir_hash(os.path.join(self.path, file))
			ziphash = ET.SubElement(addon, 'ziphash')
			
			# Make zip file from the directory
			zip_target = os.path.join(self.path, file)
			print "INFO: Packing %s" % zip_target
			try:
				zf_name = shutil.make_archive(file, "zip", self.path, file)
			except: 
				print "ERROR: Unable to make zip file."
				raise
			
			# Hash the zip file
			SHAhash = hashlib.sha1()
			try:
				zf = open(zf_name, 'rb')
				# Now hash it
				zf_hash = hashlib.sha1(zf.read()).hexdigest()
						
			except:
				print "ERROR: Couldn't open zip file %s after creating it" % zf_name
				raise
			finally:
				zf.close()
				
			ziphash.text = zf_hash
		
		xml_tree = ET.ElementTree(root)
		try:
			xml_tree.write(file_name)
		except:
			print "ERROR: Couldn't write manifest XML file %s" % file_name
		
	def read_manifest(self, xml_data):
		"""
		Reads a manifest from XML and updates the manifest dictionary.
		
		Keyword arguments:
		xml_data -- XML data as a string
		"""
		
		import xml.etree.ElementTree as ET
		
		manifest_dictionary = {}
		
		try:
			tree = ET.fromstring(xml_data)
		#except:
		#	print "ERROR: Couldn't open manifest XML file %s" % file_name
		
		#try:
			
			self.address = tree.find("address").text
			self.servername = tree.find("servername").text
			self.manifestaddress = tree.find("manifestaddress").text
			
			addon_tree = tree.find("addons")
			
			s = tree.find("addonline")
			if s is not None:
				# Provided, use it
				self.addonline = s.text
			else:
				# Didn't exist, build it
				self.addonline = ""
				for addon in addon_tree:
					self.addonline = self.addonline + addon.find("dir").text + ";"
			
			for addon in addon_tree:
				manifest_dictionary[addon.find("dir").text] = [addon.find("dirhash").text, addon.find("ziphash").text]
			
			self.manifest_dictionary = manifest_dictionary
		except:
			print "ERROR: Malformed manifest XML."
			raise
			
if __name__ == "__main__":
	m = Manifest(address = "file:///C:/Users/Ryan%20Schultz/Workspace/TaoSync/TestDirectory/", manifestaddress = "file:///C:/Users/Ryan%20Schultz/Workspace/TaoSync/TestDirectory/test_manifest.xml", servername = "SimHQ O.N.E. ACE")
	m.make_file_list("TestDirectory", True)
	print "------------------------"
	print m.address
	print m.file_list
	print "------------------------"
	m.build_manifest("test_manifest.xml")
	print "------------------------"
	m.read_manifest("test_manifest.xml")
	print m.address
	print m.manifest_dictionary